witcher
Track and put down bugs using simple concise error handling
Avoid terminating execution randomly in your code with panics
via unwrap
and expect
varients,
or laboriously writing custom enum wrapers, or having to work with Box
types which is messy;
instead use Result<T>
from witcher as the return type and wrap
errors easily for additional
contextual messaging automatically propogated up the stack. witcher implements std::error::Error
retaining downcasting and chaining. Best of all witcher provides the holy grail: automatic simplified backtraces
.
What you get
- Error handling simplified
by providing type matching on errors
by automatically handling conversions
by providing concise and terse user interaction
by providing conditional colored output - Error handling that tells the full story
by implementing
std::error::Error
by chaining errors together
by providing contextual messaging
by providing tracing from point of origin - Safety
100% safe code without any use of
unsafe
Zero low level TraitObject manipulation
Well tested with over 90% code coverage
Quick links
Manifesto
Coming from a Golang background, most recently, I fully expected to just import the defacto standard
error package in Rust similar to something like Golang's pkg/errors
and I'd be off to the races. Instead, as I dug, I found a rich anthropological layered history of
a myriad of projects and authors all professing nobal ideals and principles all trying to solve the
same issue. Rust's error handling story isn't full featured enough by itself yet. It feels a lot like
Golang's before the existance of pkg/errors
. I found a few projects clearly more used than others
and saw the tide turn on once popular packages. Literally weeks of research and testing of numerous
different patterns and packages later though I have still yet to find anything as simple and usable
as the venerable pkg/errors. Thus witcher
was born.
As a side note I moved all my research on low level TraitObject manipulation, when I was going down
the rabbit hole, over to phR0ze/rust-examples and am happy
to say witcher
is 100% safe code.
Usage
Use the wrap
extension method on Result
types to wrap the error with additional contextual
messaging and automatically chain errors together. wrap
returns a Result<T>
so there are fewer
symbols and less typing needed.
Requires rustc >= 1.30
This minimum rustc requirement is driven by the enhancements made to Rust's std::error::Error
handling improvements
- Import witcher in your
Cargo.toml
and keep debug symbols[] = "0.1" [] = true
- Use the witcher prelude
use *;
- Use the
Result<T>
alias as your return type;
- Use the
wrap
extension method on Result to provide context
Color
Color is automatically controlled by the gory based on tty
detection. You can disable color manually by setting the TERM_COLOR
environment variable to
something falsy see gory docs on controlling use.
Downcasting
We can match on error types using downcasting or with the match_err!
macro.
downcast_ref
- access std::error::Error's downcast_ref
use *;
// Wrap our internal error with additional context as we move up the stack
// Function that returns an external error type outside our codebase
Results:
$ cargo run -q --example downcast_ref
Root cause is std::io::Error: Oh no, we missed!
match_err!
- matches on concrete error typese
use *;
Results:
$ cargo run -q --example downcast_match
Root cause is std::io::Error: Oh no, we missed!
pass
- pass error through transparently
In some cases it might be nice to be able to use the Witcher common error pattern but treat particular top level errors as a pass through.
WARNING: this will not work with the match_err!
macro which uses the type directly for matching
rather than indirectly when working with an Error
type.
use *;
Results:
$ cargo run -q --example pass
Root cause is std::io::Error: Oh no, we missed!
Chaining
We can continue to leverage std::error::Error's source
method for chaining of errors. The first
error wrapped will retain its concrete type but errors there after in the chain have lost that
information.
source
- std::error::Error's source method is exposed
use *;
;
Results:
$ cargo run -q --example chain
Found witcher::Error: Failed doing super hero work
Found SuperError: SuperError is here!
Found SuperErrorSideKick: SuperErrorSideKick is here!
Retries
We can retry failing code with a few different Result
extension functions.
err_is
- will return true if an error exists and is the given type
use *;
Results:
cargo run -q --example retry_err_is
retrying using err_is #1
retrying using err_is #2
retrying using err_is #3
error: witcher::Error: Failed while attacking beast
cause: std::io::error::Error: Oh no, we missed!
symbol: retry_err_is::retry_on_concreate_error_type_using_err_is
at: examples/retry_err_is.rs:11:5
symbol: retry_err_is::main
at: examples/retry_err_is.rs:18:22
retry_on
- is a cleaner simplified way to do a similar thing as our err_is example
use *;
Results:
cargo run -q --example retry_on
std::io::Error: retrying! #1
std::io::Error: retrying! #2
std::io::Error: retrying! #3
error: witcher::Error: Failed while attacking beast
cause: std::io::error::Error: Oh no, we missed!
symbol: retry_on::retry_on_concreate_error_type
at: examples/retry_on.rs:4:5
symbol: retry_on::main
at: examples/retry_on.rs:16:22
retry
- is similar to retry_on
but doesn't take the type of error into account
use *;
Results:
cargo run -q --example retry
std::io::Error: retrying! #1
std::io::Error: retrying! #2
std::io::Error: retrying! #3
error: witcher::Error: Failed while attacking beast
cause: std::io::error::Error: Oh no, we missed!
symbol: retry::retry
at: examples/retry.rs:4:5
symbol: retry::main
at: examples/retry.rs:16:22
Display
Witcher's Error
type implements different functionality for each of the Display
format options.
They follow a level of verbosity in witcher from least information to most i.e. {} {:#} {:?} {:#?}
Normal: {}
- will write out the first error message only
Alternate: {:#}
- will write out all error messages in the chain
Debug: {:?}
- will write out all error messaging with simplified backtracing
Alternate Debug: {:#?}
- will write out all error messaging with simplified backtracing
Contribute
Pull requests are always welcome. However understand that they will be evaluated purely on whether or not the change fits with my goals/ideals for the project.
Git-Hook
Enable the git hooks to have automatic version increments
License
This project is licensed under either of:
- MIT license LICENSE-MIT or http://opensource.org/licenses/MIT
- Apache License, Version 2.0 LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Backlog
- Add rust doc comments
- Mechanism for converting to JSON maybe use
{:#?}
Changelog
- 12/30/2020
- Corrected the minimum required
rustc
badge
- Corrected the minimum required